/**
 * Copyright ©2014-2019 Youzan.com All rights reserved
 * me.wbean.plugin.dubbo.invoker
 */
package com.linkoog.devtools.dubbo.invoke.telnet;

import com.linkoog.devtools.dubbo.invoke.DubboInvoker;
import com.linkoog.devtools.dubbo.invoke.DubboRequest;
import com.linkoog.devtools.net.telnet.TelnetClient;
import com.linkoog.devtools.utils.Assert;
import com.linkoog.devtools.utils.IOUtils;
import com.linkoog.devtools.utils.JacksonUtil;
import com.linkoog.devtools.utils.StringUtils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 *
 * @author wbean
 * @date 2019/2/13 下午3:33
 */
public class TelnetDubboInvoker implements DubboInvoker {


    /**
     * 服务器返回的结尾的最后字符
     */
    private static final char OUT_ENDING_LAST_CHAR = '>';
    /**
     * 服务器返回的结尾
     */
    private static final String OUT_ENDING = "dubbo>";

    private Charset charset = StandardCharsets.UTF_8;


    public String invoke(DubboRequest dubboRequest) {
        String ip = dubboRequest.getIp();
        int port = dubboRequest.getPort();

        String address = ip + ":"+port;
        TelnetClient telnet = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            telnet = Assert.callable("connection server failed: " + address, () -> {
                TelnetClient telnetClient = new TelnetClient("VT220");
                telnetClient.setConnectTimeout(10000);
                telnetClient.connect(ip, port);
                telnetClient.setSoTimeout(6000);
                return telnetClient;
            });


            outputStream = Assert.callable("fail to open OutputStream", telnet::getOutputStream);
            inputStream = Assert.callable("fail to open inputStream", telnet::getInputStream);
            PrintWriter printWriter = new PrintWriter(outputStream, true);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));


            String noSuchService = "No such service " + dubboRequest.getInface();
            StringBuilder commandBuilder = new StringBuilder("invoke ");
            commandBuilder.append(dubboRequest.getInface()).append(".");
            commandBuilder.append(dubboRequest.getMethodName()).append("(");
            commandBuilder.append(StringUtils.join(Arrays.stream(dubboRequest.getParamObj()).map(JacksonUtil::toJson).collect(Collectors.toList()), ","));
            commandBuilder.append(")");

            String command = commandBuilder.toString();
            writeIn(printWriter, command);
            //读取服务器返回信息
            String response = readOut(inputStream);
            Assert.isTrue(! response.contains(noSuchService), noSuchService);


            //匹配到多个方法时，使用select命令请求
            if (response.startsWith("Methods:")){
                String[] lines =  response.split("\r\n");
                boolean findMethod = false;
                for (int i = 1; i < lines.length; i++) {
                    String line = lines[i];
                    SelectMethod selectMethod = parseSelectMethod(line);
                    if (selectMethod == null) continue;

                    if (compareMethodType(dubboRequest.getParamType(), selectMethod.getParameterTypes())){
                        findMethod = true;
                        writeIn(printWriter, "select " + selectMethod.getNumber());
                        response = readOut(inputStream);
                        Assert.isTrue(! response.contains(noSuchService), noSuchService);
                        break;
                    }
                }

                Assert.isTrue(findMethod, "can not found the method");
            }


            //结果处理
            String[] lines =  response.split("\r\n");
            for (String line : lines) {
                if (line.startsWith("result:")){
                    return line.substring("result:".length()).trim();
                }
            }

            throw new IllegalArgumentException("can not found the result");
        }finally {
            IOUtils.quietClose(outputStream);
            IOUtils.quietClose(inputStream);
            IOUtils.quietClose(telnet);
        }
    }

    private void writeIn(PrintWriter in, String value) {
        in.println(value);
        in.flush();
    }

    private String readOut(InputStream out)  {
        String result = Assert.callable("fail to read response from server", () -> {
            StringBuilder stringBuilder = new StringBuilder();
            try {
                char c = (char) out.read();
                while (true) {
                    stringBuilder.append(c);
                    if (c == OUT_ENDING_LAST_CHAR) {
                        if (stringBuilder.toString().endsWith(OUT_ENDING)) {
                            byte[] temp = stringBuilder.toString().getBytes("iso8859-1");
                            return new String(temp, charset);
                        }
                    }
                    c = (char) out.read();
                }
            }catch (SocketTimeoutException ignore){
                return stringBuilder.toString();
            }
        });

        Assert.isNotBlank(result, "timeout to read response");
        Assert.isTrue(!result.startsWith("Command: ls disabled"),  "telnet Command is disabled: [ls]");
        Assert.isTrue(!result.startsWith("Command: invoke disabled"),  "telnet Command is disabled: [invoke]");
        Assert.isTrue(!result.startsWith("Command: select disabled"),  "telnet Command is disabled: [select]");
        return result;
    }

}
